﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Protokol;


namespace SahServer
{
	class Program
	{
		// Indikator da li se server gasi.
		private static bool terminate;
		public static bool Terminate
		{
			get
			{
				return terminate;
			}
		}
		// Promenljiva koja prati identitet svake konekcije.
		private static int ClientNumber = 0;
		private static TcpListener listener;
		public static void Main()
		{
			try
			{
				string Server = getFileAsString("C:\\Server.txt");
				//Ovde bi trebalo da ide neka vrsta detekcije IP adrese ovog računara
				listener = new TcpListener(IPAddress.Parse(Server), Zajednicko.PortNaServeru);
				Console.WriteLine("Počinje osluškivanje tcp konekcije na portu " + Zajednicko.PortNaServeru + "...");
				terminate = false;
				listener.Start();

				listener.BeginAcceptTcpClient(ConnectionHandler, null);
				// Server će raditi dok se ne klikne na [Enter]
				Console.WriteLine("Server očekuje konekciju. \nEnter za zaustavljanje servera.");
				Console.ReadLine();
			}
			catch (Exception ex)
			{
				Util.Log2txt(ex, "Main Server");
			}
			finally
			{
				// Shut down the TcpListener. This will cause any outstanding
				// asynchronous requests to stop and throw an exception in
				// the ConnectionHandler when EndAcceptTcpClient is called.
				// More robust termination synchronization may be desired here,
				// but for the purpose of this example ClientHandler threads are
				// all background threads and will terminate automatically when
				// the main thread terminates. This is suitable for our needs.
				//Gašenje TcpListener-a. Ovo može izazvati izuzetke ConnetionHandler metodi. 
				//U svakom slučaju kada se glavni program ugasi, ugasiće se i sve niti.
				
				Console.WriteLine("Zaustavljanje servera...");
				terminate = true;
				if (listener != null)
					listener.Stop();
			}
			Console.WriteLine(Environment.NewLine);
			Console.WriteLine("Server zaustavljen. Pritisnite Enter");
			Console.ReadLine();
		}
		public static string getFileAsString(string fileName)
		{
			StreamReader sReader = null;
			string contents;
			try
			{
				FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
				sReader = new StreamReader(fileStream);
				contents = sReader.ReadToEnd();
			}
			finally
			{
				if (sReader != null)
				{
					sReader.Close();
				}
			}
			return contents;
		}

		/// <summary>
		/// A method to handle the callback when a connection is established
		/// from a client. This is a simple way to implement a dispatcher
		/// but lacks the control and scalability required when implementing
		/// full-blown asynchronous server applications.
		/// </summary>
		/// <param name="result"></param>
		private static void ConnectionHandler(IAsyncResult result)
		{
			TcpClient client;
			// Always end the asynchronous operation to avoid leaks.
			try
			{
				// Get the TcpClient that represents the new client connection.
				client = listener.EndAcceptTcpClient(result);

			}
			catch (ObjectDisposedException)
			{
				return;
			}
			Console.WriteLine("Obaveštenje: Nova konekcija je prihvaćena.");
			//Osluškivanje za sledeću konekciju
			listener.BeginAcceptTcpClient(ConnectionHandler, null);
			if (client != null)
			{
				Interlocked.Increment(ref ClientNumber);
				string clientName = "Klijent " + ClientNumber;
				Console.WriteLine("Obaveštenje: Pravi se klijent({0}).", clientName);
				new ClientHandler(client, clientName);
			}
		}
	}

	static class Korisnici
	{
		public static List<Korisnik> korisniciList;

		static Korisnici()
		{
			korisniciList = new List<Korisnik>();
		}

		public static void IzbaciKorisnika(string ID)
		{
			foreach (Korisnik korisnik in korisniciList)
			{
				if (ID.Equals(korisnik.ID))
					korisniciList.Remove(korisnik);
			}
		}

		public static Korisnik NadjiKorisnikaPoIPAdresi(string IP)
		{
			foreach (Korisnik korisnik in korisniciList)
			{
				if (IP.Equals(korisnik.IP))
					return korisnik;
			}
			throw new Exception("Korisnik nije nađen");
		}

		public static Korisnik NadjiKorisnikaPoID(string ID)
		{
			foreach (Korisnik korisnik in korisniciList)
			{
				if (ID.Equals(korisnik.ID))
					return korisnik;
			}
			throw new Exception("Korisnik nije nađen");
		}

		public static Korisnik NadjiKorisnikaPoImenu(string user)
		{
			if (user != null)
				foreach (Korisnik korisnik in korisniciList)
				{
					if (user.Equals(korisnik.Naziv))
						return korisnik;
				}
			throw new UserNotFoundException("Korisnik nije nađen", user);
		}

	}

	/// <summary>
	/// A class that encapsulates the logic to handle a client connection.
	/// </summary>
	public class ClientHandler : ISahServer
	{
		private TcpClient client;

		private string ID;

		internal ClientHandler(TcpClient client, string ID)
		{
			this.client = client;
			this.ID = ID;

			Thread thread = new Thread(ProcessConnection);
			thread.IsBackground = true;
			thread.Start();
		}

		private void ProcessConnection()
		{
			using (client)
			{
				using (BinaryReader reader = new BinaryReader(client.GetStream()))
				{
					if (reader.ReadString() == Zajednicko.KlijentKontaktiraServer)
					{
						using (BinaryWriter writer = new BinaryWriter(client.GetStream()))
						{
							KlijentZahtevaKonekciju(writer);
							while (true)
							{
								string message;
								try
								{
									Console.WriteLine("Osluškujemo vezu...");
									message = reader.ReadString();
								}
								catch (Exception ex)
								{
									//Svaki izuzetak se uzima kao da je došlo do prekida veze.
									Console.WriteLine("Došlo je do greške.\n" + ex.Message);
									message = Zajednicko.KlijentZahtevaPrekidKonekcije;
								}

								if (message == Zajednicko.KlijentZahtevaPrekidKonekcije)
									break;

								Zahtev zahtev = Zajednicko.ObradiZahtev(message);

								#region switch provera
								switch (zahtev.ZahtevKlijenta)
								{
									case Zajednicko.KlijentZahtevaLogovanje:
										KlijentZahtevaLogovanje(writer, zahtev);
										break;

									case Zajednicko.KlijentZahtevaPrekidKonekcije:
										Console.WriteLine(ID + ": Prekidanje konekcije...");
										client.Close();
										break;

									case Zajednicko.KlijentZahtevaIPKorisnika:
										KlijentZahtevaIPKorisnika(writer, zahtev);
										break;

									case Zajednicko.KlijentZahtevaOdjavljivanje:
										KlijentZahtevaOdjavljivanje(writer, zahtev);
										break;

									default:
										writer.Write(Zajednicko.ServerNePrepoznajeKomandu + ":" + zahtev.ZahtevKlijenta);
										Console.WriteLine(ID + ": Nepoznata komanda:" + zahtev.ZahtevKlijenta);
										break;
								}
								#endregion
							}
						}
					}
					else
					{
						using (BinaryWriter writer = new BinaryWriter(client.GetStream()))
						{
							writer.Write(Zajednicko.ServerNePrepoznajeKomandu);
						}
						Console.WriteLine(ID + ": Nisam mogao da uspostavim vezu.");
					}
				}
			}
			Console.WriteLine(ID + ": Konekcija ka klijentu je zatvorena.");
			try
			{
				Korisnik k = Korisnici.NadjiKorisnikaPoID(ID);
				Korisnici.korisniciList.Remove(k);
				Console.WriteLine("Korisnik je izbačen sa liste");
			}
			catch (Exception)
			{
				Console.WriteLine("Korisnik se već odjavio");
			}

		}

		#region ISahServer Members

		public void KlijentViseNeIgra(Zahtev zahtev)
		{
			throw new NotImplementedException();
		}

		public void KlijentIgra(Zahtev zahtev)
		{
			throw new NotImplementedException();
		}

		#endregion

		public void KlijentZahtevaKonekciju(BinaryWriter writer)
		{
			writer.Write(Zajednicko.ServerPrihvataKlijenta);
			Console.WriteLine(ID + ": Uspostavljena konekcija.");
		}

		public void KlijentZahtevaLogovanje(BinaryWriter writer, Zahtev zahtev)
		{
			Korisnik k = new Korisnik();
			try
			{
				if (zahtev.Podaci == "Error" || zahtev.Podaci == "")
					throw new Exception("Loše formiran zahtev");
				// Preuzima se korisnik iz zahteva?!
				k.Naziv = zahtev.Podaci;
				string[] temp = client.Client.RemoteEndPoint.ToString().Split(Convert.ToChar(":"));
				k.IP = temp[0];
				k.port = Convert.ToInt16(zahtev.Podaci1);
				k.ID = ID;

				// Pokušava se da se ubaci u listu...
				foreach (Korisnik korisnik in Korisnici.korisniciList)
				{
					if (k.Naziv == korisnik.Naziv)
						throw new Exception("Korisnik već postoji");
				}
				Korisnici.korisniciList.Add(k);
				// Ako je ubačen vraća se klijentu OK...
				writer.Write(Zajednicko.ServerPrihvataLogovanje + ":" + k.Naziv);
				Console.WriteLine("Unet je korisnik " + k.Naziv + " sa IP adresom: " + k.IP + " na portu: " + k.port);
			}
			catch (Exception ex)
			{
				if (ex.Message == "Korisnik već postoji")
				// A ako nije vraća se error...
				{
					writer.Write(Zajednicko.ServerOdbijaLogovanjeZbogZauzeca + "\n" + k.Naziv);
					Console.WriteLine("Korisnik je već logovan");
					Util.Log2txt(ex, "KlijentzahtevaLogovanje");
				}

				else
				// Nepoznata greška prilikom logovanja
				{
					writer.Write(Zajednicko.ServerOdbijaLogovanje);
					Console.WriteLine("UPS, nepoznata greška prilikom logovanja: " + ex.Message);
					Util.Log2txt(ex, "KlijentzahtevaLogovanje");
				}
			}
		}

		public void KlijentZahtevaIPKorisnika(BinaryWriter writer, Zahtev zahtev)
		{
			string zahtevanikorisnik = zahtev.Podaci;
			try
			{
				Korisnik k = Korisnici.NadjiKorisnikaPoImenu(zahtevanikorisnik);
				if (k.Naziv == zahtevanikorisnik)
					writer.Write(Zajednicko.ServerVracaIPKorisnika + ":" + k.IP + ":" + k.port);
				else
					throw new UserNotFoundException("Korisnik nije nađen", zahtevanikorisnik);
			}
			catch (UserNotFoundException)
			{//Korisnik nije pronađen
				writer.Write(Zajednicko.ServerNeNalaziKorisnika);
			}
			catch (Exception)
			{
				writer.Write(Zajednicko.ServerImaProblemaSaKorisnikom);
			}
		}

		public void KlijentZahtevaOdjavljivanje(BinaryWriter writer, Zahtev zahtev)
		{
			try
			{
				bool signal = false;
				foreach (Korisnik korisnik in Korisnici.korisniciList)
				{
					if (korisnik.Naziv == zahtev.Podaci)
					{
						Korisnici.korisniciList.Remove(korisnik);
						writer.Write(Zajednicko.ServerPrihvataOdjavljivanje + ":" + korisnik.Naziv);
						Console.WriteLine(ID + ": Odjavio se korisnik: " + korisnik.Naziv);
						signal = true;
						break;
					}
				}
				if (!signal)
					throw new Exception("Korisnik nije prijavljen.");
			}
			catch (Exception ex)
			{
				Console.WriteLine("Problem pri odjavljivanju korisnika.\n" + ex.Message);
				Util.Log2txt(ex, "KlijentZahtevaOdjavljivanje");
			}
		}
	}

	public class UserNotFoundException : Exception
	{
		public string UserName;

		public UserNotFoundException(string poruka, string userName)
			: base(poruka)
		{
			UserName = userName;
		}
	}
}
